package Lista04;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Transfery 
{
	public static final Class<? extends Konto> KLASA_KONTA = KontoNiepewne.class;
//	public static final Class<? extends Konto> KLASA_KONTA = KontoSynchronized.class;
//	public static final Class<? extends Konto> KLASA_KONTA = KontoSemaphore.class;
//	public static final Class<? extends Konto> KLASA_KONTA = KontoReentrantLock.class;
	
    public static final int ILOSC_KONT = 100;
    public static final int ILOSC_MAKLEROW = 10;
    public static final int ILOSC_POCZATKOWA_KASY_NA_KONCIE = 100;
    public static final int ILOSC_OPERACJI = 100;
    
    private List<Konto> konta = new ArrayList<Konto>(ILOSC_KONT);
    
    public void przygotujKonta() throws Exception
    {
        for (int i = 0 ; i < ILOSC_KONT ; ++i)
        {
        	Konto konto = KLASA_KONTA.newInstance();
        	konto.inicjuj(ILOSC_POCZATKOWA_KASY_NA_KONCIE);
            konta.add(konto);
        }
    }
    
    public void dokonajTransferow() throws InterruptedException
    {
        List<Thread> watki = new ArrayList<Thread>(ILOSC_MAKLEROW);
        for(int i = 0 ; i < ILOSC_MAKLEROW ; ++i)
        {
            watki.add(new Thread(new Makler(i,konta, ILOSC_OPERACJI)));
        }
        
        for(Thread watek : watki)
        {
            watek.start();
        }
        
        for(Thread watek : watki)
        {
        	watek.join();
        }
        System.out.println("Transfery dokonane!");
    }
    
    public void sprawdzSumeKasyZKont()
    {
        int powinnoByc = ILOSC_KONT * ILOSC_POCZATKOWA_KASY_NA_KONCIE;
        int jest = 0;
        for(Konto k : konta)
        {
            jest += k.stan();
        }
        if(powinnoByc != jest)
        {
            System.out.println("Kasy powinno byc : " + powinnoByc + " ,a jest : "+ jest);
        }
        else
        {
            System.out.println("Jest OK");
        }
    }

    public static void main(String[] args) throws Exception 
    {
        Transfery t = new Transfery();
        t.przygotujKonta();
        t.dokonajTransferow();
        t.sprawdzSumeKasyZKont();
    }

}

class Makler implements Runnable
{
    private int iloscOperacji;
    private List<Konto> konta;
    private int numer;
    
    public Makler(int numer,List<Konto> konta,int iloscOperacji)
    {
        this.konta = konta;
        this.iloscOperacji = iloscOperacji;
        this.numer = numer;
    }
    
    @Override
    public void run() {
        System.out.println("Czesc! jestem makler numer " + numer);
        Random rnd = new Random();
        for (int i = 0 ; i < iloscOperacji ; ++i)
        {
            int idxNr1 = rnd.nextInt(konta.size());
            int idxNr2 = rnd.nextInt(konta.size());
            
            Konto konto1 = konta.get(idxNr1);
            Konto konto2 = konta.get(idxNr2);
            
            konto2.wplac(konto1.wyplac(1));
        }
        System.out.println("Makler numer "+ numer + " skonczyl.");
    }
}

interface Konto
{
    public int wyplac(int ile);
    public void wplac(int ile);
    public int stan();
    public void inicjuj(int ile);
}

class KontoNiepewne implements Konto
{
    private int stanKonta;
    
    public void inicjuj(int ile)
    {
        this.stanKonta = ile;
    }
    public int wyplac(int ile)
    {
        int retVal = 0;
        if(ile <= stanKonta)
        {
            stanKonta -= ile;
            retVal = ile;
        }
        else
        {
            retVal = stanKonta;
            stanKonta = 0;   
        }
        return retVal;
    }
    public void wplac(int ile)
    {
        stanKonta += ile;
    }
    public int stan()
    {
        return stanKonta;
    }
}

class KontoSynchronized extends KontoNiepewne
{
    @Override
    public synchronized int wyplac(int ile) 
    {
        return super.wyplac(ile);
    }
    @Override
    public synchronized void wplac(int ile) 
    {
        super.wplac(ile);
    }
}

class KontoSemaphore extends KontoNiepewne
{
	private Semaphore sem = new Semaphore(1);
    @Override
    public int wyplac(int ile) 
    {
    	try 
    	{
			sem.acquire();
		}
    	catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
    	int retVal = super.wyplac(ile); 
        sem.release();
    	return retVal;
    }
    @Override
    public void wplac(int ile) 
    {
    	try 
    	{
			sem.acquire();
		}
    	catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
    	super.wplac(ile); 
        sem.release();
    }
}

class KontoReentrantLock extends KontoNiepewne
{
	Lock lock = new ReentrantLock();
    @Override
    public int wyplac(int ile) 
    {
    	int retVal;
    	lock.lock();
        retVal = super.wyplac(ile);
        lock.unlock();
        return retVal;
    }
    @Override
    public void wplac(int ile) 
    {
    	lock.lock();
        super.wplac(ile);
        lock.unlock();
    }
}


